//
//	IOBUS - transport protocol
//
//  (c) 2008 mocom software GmbH & Co KG
//
//  $Author: volker $
//  $Date: 2009-10-01 18:51:54 +0200 (Do, 01. Okt 2009) $
//  $Revision: 509 $
//
//-------------------------------------------------------------------------------------------
//

#include <tinyhal.h>
#define DEF_NO_BASIC_TYPES	1
#include <iobus.h>


// basic states
enum
{
	msDISABLED,		// SWITCHED OFF
	msENABLE,		// ENABLING
	msCONFIG,		// CONFIG, DETECT CONFIGURED NODES
	msTECHIN,		// LEARN NODE CAPABILITIES
	msOPERATION,	// I/O CYCLE 
};

static int                      master_state;
static int                      previous_state;
static int                      current_node = 0;
static unsigned 				iobus_cycle;
static unsigned 				pending;
static unsigned					cycles;
static UINT32 					configured;
static UINT32 					detected;
static UINT32 					teachedin;
static IOBusObjectDir 			node_object_dir[IOBUS_MAX_NODES];
static IOBusObjectWireImage 	node_data_pending[IOBUS_MAX_NODES];
static IOBusObjectWireImage 	txdata;
static INT8					    updated[IOBUS_MAX_NODES];
static unsigned					timeouts[IOBUS_MAX_NODES];
static int						loops = 0;
static int						active_nodes = 0;
static unsigned 				cycle_time_in_ms=0;
static int 						cycle_mode_on;


void IOBusSetCycleMode(int mode)
{
	cycle_mode_on = mode;
}

void IOBusReadIDString( UINT8 node )
{
	current_node = node;
	pending = IO_CMD_SysReadId;
	IOBusSendCmd( node, pending, iobus_cycle, 0, NULL );
}

void IOBusPoll( UINT8 node )
{
	unsigned error = 0;
	unsigned len;

	current_node = node;
	pending = IO_CMD_Update;
	len = IOBusObjectSerialize( &node_object_dir[node], &txdata, iobus_cycle, &error, OBJ_IS_OUTPUT );
	IOBusSendCmd( node, IO_CMD_Update, iobus_cycle, len, ( UINT8* ) &txdata );
}

void IOBusReadInfo( UINT8 node )
{
	current_node = node;
	pending = IO_CMD_ReadInfo;
	IOBusSendCmd( current_node, pending, iobus_cycle, 0, NULL );
}

static char *get_state_name( int state )
{
	switch ( state )
	{
		case msDISABLED:
			return "msDISABLED";

		case msENABLE:
			return "msENABLE";

		case msCONFIG:
			return "msCONFIG";

		case msTECHIN:
			return "msTECHIN";

		case msOPERATION:
			return "msOPERATION";

		default:
			return "UNKNOWN";
	}
}

void IOBusSwitchState( int newstate )
{
	previous_state = master_state;
	master_state = newstate;
	loops=0;
	
	debug_printf("new=%s\r\n",get_state_name(newstate));

	switch ( newstate )
	{
		case msDISABLED:
			break;

		case msENABLE:
			break;
			
		case msCONFIG:
			detected =
			teachedin = 
			active_nodes = 0;
			for ( int i = 0;i < IOBUS_MAX_NODES;i++ )
			{
				timeouts[i] = 0;
				updated[i] = FALSE;
			}
			IOBusReadIDString( 0 );
			break;

		case msTECHIN:
			IOBusReadInfo( 0 );
			break;

		case msOPERATION:
			IOBusPoll( 0 );
			break;

		default:
			debug_printf( "ERROR, Try to switch to unknown state %u\r\n", newstate );
			break;
	}
	//debug_printf("%u->%u\r\n",previous_state,master_state);
}

static unsigned incr_current_node( )
{
	while ( 1 )
	{
		if (configured == 0) {
			current_node = 0;
			IOBusSwitchState(msDISABLED);
			return 0;
		}
			
		current_node++;
		if ( current_node >= IOBUS_MAX_NODES )
		{
			loops++;
			current_node = 0;
			return 0;
		}

		// when scanning the network first, we want configured only
		if ( configured & ( 1 << current_node ) )
		{
			return current_node;
		}
	}
}

int IOBusMasterIndication( IOBusFrame *frame )
{
	switch ( master_state )
	{
		case msDISABLED:
			return FALSE;
			
		case msENABLE:
			// wait some time
			loops++;
			if (loops > 10)
				IOBusSwitchState( msCONFIG );
			break;

		case msCONFIG:
			// we poll the net using IO_CMD_SysReadId
			if ( frame && pending == IO_CMD_SysReadId && frame->len != 0 )
			{
				debug_printf( "NODE %u %s found. %u IOObjects.\r\n", current_node, &frame->data[1], frame->data[0] );
				node_object_dir[current_node].entries = frame->data[0];
				detected |= (1 << current_node);
				active_nodes++;
			}
			if ( incr_current_node() == 0 )
			{
				if ( detected == configured)
				{
					IOBusSwitchState( msTECHIN );
				}
				else if (loops > 4)
				{
					IOBusKickApiError( 0, IO_ERR_ENABLEFAILED );
					IOBusSwitchState( msDISABLED );
				} else {
					IOBusReadIDString( 0 );
				}
			}
			else
			{
				IOBusReadIDString( current_node );
			}
			break;

		case msTECHIN:
			// we poll the net using IO_CMD_SysReadId
			if ( frame && pending == IO_CMD_ReadInfo  && frame->len != 0 )
			{
				IOBusObjectDeSerializeInfo( &node_object_dir[frame->addr], ( IOBusObjectWireImage * ) frame->data, frame->cycle );
				teachedin |= 1 << current_node;
			}
			if ( incr_current_node() == 0 )
			{
				if ( teachedin == detected )
				{
					cycles = 0;
					IOBusSwitchState( msOPERATION );
				}
				else if ( loops > 4 )
				{
					IOBusKickApiError( 0, IO_ERR_TIMEOUT );
					IOBusSwitchState( msENABLE );
				} 
				else 
				{
					IOBusReadInfo( current_node );
				}
			}
			else
			{
				IOBusReadInfo( current_node );
			}
			break;

		case msOPERATION:
			if ( frame )
			{
				if ( pending == IO_CMD_Update && frame->len )
				{
					// copy data to
					IOBusHalMemoryCopy( &node_data_pending[frame->addr] , frame->data, frame->len );
					updated[frame->addr]++;
					timeouts[current_node] = 0;
					if ( frame->cmdres & IO_ERR_FATAL_CLASS )
					{					
						IOBusKickApiError(( frame->addr ), frame->cmdres & IO_ERR_MASK );
					}
				}
			}
			else
			{
				// constant timeout handling here
				timeouts[current_node]++;
				if ( timeouts[current_node] > 4 )
				{
					IOBusKickApiError( current_node, IO_ERR_TIMEOUT );
					timeouts[current_node] = 0;					
				}
			}
			if ( incr_current_node() == 0 )
			{
				// if cycle mode, we update teh IODIRECTORY only once a second
				if (cycle_mode_on) {
					cycles++;
					if ((cycles * cycle_time_in_ms * active_nodes) > 1000)
					{
						IOBusCycle();
						cycles = 0;
					}
				}
				else
				{
					IOBusCycle();
				}
			}
			IOBusPoll( current_node );
			break;

		default:
			debug_printf( "ERROR, NODE %u bad response in UNKNOWN STATE\r\n", current_node );
			break;
	}	
	return TRUE;
}

int IOBusCycle()
{
	if ( master_state == msOPERATION )
	{
		// import pending data objects
		for ( int i = 0;i < IOBUS_MAX_NODES;i++ )
		{
			if ( updated[i] )
			{
				IOBusObjectDeSerialize( &node_object_dir[i], &node_data_pending[i], iobus_cycle );
				updated[i] = 0;
			}
		}

		// inform API about new io
		if (cycle_mode_on)
			IOBusKickApi( iobus_cycle, 0 );

		// send new tick
		iobus_cycle = ( iobus_cycle + 1 ) & 0x7f;
		current_node = 0;
	}
	
	return !( previous_state == msDISABLED );
}

static IOBusObject *get_object( int node, int obj )
{
	if ( node >= 0 && node < IOBUS_MAX_NODES && obj >= 0 && obj < node_object_dir[node].entries )
	{
		return &node_object_dir[node].objects[obj];
	}
	return NULL;
}

int IOBusMasterGetNodeState( int node )
{
	if ( node >= 0 && node < IOBUS_MAX_NODES )
	{
		return ( detected & ( 1 << node ) ) != 0;
	}

	return -1;
}

int IOBusMasterGetObjects( int node )
{
	if ( node >= 0 && node < IOBUS_MAX_NODES )
	{
		return node_object_dir[node].entries;
	}
	return 0;
}

int IOBusMasterGetClass( int node, int objidx )
{
	IOBusObject * obj = get_object( node, objidx );
	if ( obj )
	{
		return obj->id & OBJ_CLASS_MASK;
	}
	return 0;
}

int IOBusMasterGetType( int node, int objidx ) 
{
	IOBusObject * obj = get_object( node, objidx );
	if ( obj )
	{
		return obj->id & OBJ_TYPE_MASK;
	}
	return 0;
}

int IOBusMasterGetExponent( int node, int objidx ) 
{
	int e;
	IOBusObject * obj = get_object( node, objidx );
	if ( obj )
	{
		// shift sign bit to msb
		e = (obj->id & OBJ_EXP_MASK) << 8;
		// shift down
		return (int) e >> 24;
	}
	return 0;
}

int IOBusMasterGetPhysicalUnit( int node, int objidx )
{
	IOBusObject * obj = get_object( node, objidx );
	if ( obj )
	{
		return obj->id & OBJ_UNIT_MASK;
	}
	return 0;
}

int IOBusMasterGetValue( int node, int obj, IOBusObjectValue *value )
{
	unsigned cycle;
	
	if ( node >= 0 && node < IOBUS_MAX_NODES)
	{
		return IOBusObjectReadData(&node_object_dir[node],(unsigned)obj,value,&cycle);
	}
	return false;
}

int IOBusMasterSetValue( int node, int obj, IOBusObjectValue *value )
{
	if ( node >= 0 && node < IOBUS_MAX_NODES)
	{
		return IOBusObjectWriteData(&node_object_dir[node],(unsigned)obj,value,iobus_cycle);
	}
	return false;
}

int IOBusMasterEnabledState(  )
{
	return master_state;
}


void IOBusMasterInit( unsigned long config, unsigned cycle_in_ms )
{
	configured = config;
		
	if ( config )
	{
		// save 
		cycle_time_in_ms = cycle_in_ms;
		
		// reset link layer
		IOBusInitLinkLayer( 0xff );

		for ( int i = 0;i < IOBUS_MAX_NODES;i++ )
		{
			updated[i] = 0;
		}

		current_node = loops =0;
		IOBusSwitchState( msENABLE );
	}
	else
	{
		IOBusSwitchState( msDISABLED );
	}
}
